/** * Copyright (c) 2012 to original author or authors All rights reserved. This program and the * accompanying materials are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package io.takari.aether.okhttp; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Proxy.Type; import java.net.SocketAddress; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import io.takari.aether.client.AetherClient; import io.takari.aether.client.AetherClientAuthentication; import io.takari.aether.client.AetherClientConfig; import io.takari.aether.client.AetherClientProxy; import io.takari.aether.client.Response; import io.takari.aether.client.RetryableSource; import okhttp3.Authenticator; import okhttp3.Credentials; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Route; import okhttp3.internal.tls.OkHostnameVerifier; import okio.BufferedSink; public class OkHttpAetherClient implements AetherClient { private final Authenticator PROXY_AUTH = new Authenticator() { @Override public Request authenticate(Route route, okhttp3.Response response) throws IOException { if (response.code() == HttpURLConnection.HTTP_PROXY_AUTH) { return authenticateProxy(route.proxy(), response); } return null; } private Request authenticateProxy(Proxy proxy, okhttp3.Response response) throws IOException { Request req = response.request(); if(req.header("Proxy-Authorization") == null && config.getProxy() != null && config.getProxy().getAuthentication() != null) { String value = toHeaderValue(config.getProxy().getAuthentication()); boolean tunneled = req.isHttps() && proxy.type() == Type.HTTP; if(!tunneled) { // start including proxy-auth on each request headers.put("Proxy-Authorization", value); } return req.newBuilder().header("Proxy-Authorization", value).build(); } return null; } }; private final Map<String, String> headers; private final AetherClientConfig config; private final OkHttpClient httpClient; public OkHttpAetherClient(AetherClientConfig config) { this.config = config; // headers are modified during http auth handshake // make a copy to avoid cross-talk among client instances headers = new HashMap<String, String>(); if (config.getHeaders() != null) { headers.putAll(config.getHeaders()); } // // If the User-Agent has been overriden in the headers then we will use that // if (!headers.containsKey("User-Agent")) { headers.put("User-Agent", config.getUserAgent()); } OkHttpClient.Builder builder = new OkHttpClient.Builder() // .proxy(getProxy(config.getProxy())) // .hostnameVerifier(OkHostnameVerifier.INSTANCE) // // TODO looks odd, why do I need to use the same Authenticator twice? .authenticator(PROXY_AUTH) // see #authenticate below .proxyAuthenticator(PROXY_AUTH) // .connectTimeout(config.getConnectionTimeout(), TimeUnit.MILLISECONDS) // .readTimeout(config.getRequestTimeout(), TimeUnit.MILLISECONDS); if (config.getSslSocketFactory() != null) { builder.sslSocketFactory(config.getSslSocketFactory()); } if (config.getHostnameVerifier() != null) { builder.hostnameVerifier(config.getHostnameVerifier()); } this.httpClient = builder.build(); } @Override public Response head(String uri) throws IOException { Response response; do { response = execute(httpClient, builder(uri, null).head().build()); } while (response == null); return response; } @Override public Response get(String uri) throws IOException { Response response; do { response = execute(httpClient, builder(uri, null).get().build()); } while (response == null); return response; } @Override public Response get(String uri, Map<String, String> requestHeaders) throws IOException { Response response; do { response = execute(httpClient, builder(uri, requestHeaders).get().build()); } while (response == null); return response; } @Override // i need the response public Response put(String uri, final RetryableSource source) throws IOException { Response response; do { // disable response caching // connection.addRequestProperty("Cache-Control", "no-cache") may work too OkHttpClient httpClient = this.httpClient.newBuilder().cache(null).build(); final MediaType mediaType = MediaType.parse("application/octet-stream"); final RequestBody body = new RequestBody() { @Override public void writeTo(BufferedSink sink) throws IOException { source.copyTo(sink.outputStream()); } @Override public MediaType contentType() { return mediaType; } @Override public long contentLength() throws IOException { return source.length(); } }; Request.Builder builder = builder(uri, null).put(body); if (source.length() > 0) { builder.header("Content-Length", String.valueOf(source.length())); } response = execute(httpClient, builder.build()); } while (response == null); return response; } private Response execute(OkHttpClient httpClient, Request request) throws IOException { okhttp3.Response response = httpClient.newCall(request).execute(); switch (response.code()) { case HttpURLConnection.HTTP_UNAUTHORIZED: if (config.getAuthentication() != null && !headers.containsKey("Authorization")) { headers.put("Authorization", toHeaderValue(config.getAuthentication())); response.body().close(); // help connection pool reclaim the connection return null; // retry } break; } return new ResponseAdapter(response); // do not retry } private String toHeaderValue(AetherClientAuthentication auth) { return Credentials.basic(auth.getUsername(), auth.getPassword()); } private java.net.Proxy getProxy(AetherClientProxy proxy) { java.net.Proxy ohp; if (proxy == null) { ohp = java.net.Proxy.NO_PROXY; } else { SocketAddress addr = new InetSocketAddress(proxy.getHost(), proxy.getPort()); ohp = new java.net.Proxy(java.net.Proxy.Type.HTTP, addr); } return ohp; } private Request.Builder builder(String uri, Map<String, String> requestHeaders) throws IOException { Request.Builder builder = new Request.Builder().url(uri); // Headers if (headers != null) { for (String headerName : headers.keySet()) { builder.addHeader(headerName, headers.get(headerName)); } } if (requestHeaders != null) { for (String headerName : requestHeaders.keySet()) { builder.addHeader(headerName, requestHeaders.get(headerName)); } } return builder; } class ResponseAdapter implements Response { okhttp3.Response conn; ResponseAdapter(okhttp3.Response conn) { this.conn = conn; } @Override public int getStatusCode() throws IOException { return conn.code(); } @Override public String getStatusMessage() throws IOException { return conn.message(); } @Override public String getHeader(String name) { return conn.header(name); } @Override public Map<String, List<String>> getHeaders() { Map<String, List<String>> headers = new HashMap<String, List<String>>(); for (String header : conn.headers().names()) { headers.put(header, conn.headers(header)); } return headers; } @Override public InputStream getInputStream() throws IOException { return conn.body().byteStream(); } } @Override public void close() {} }